package com.zhuanzhuan.hero.user.util;

import com.google.common.base.Throwables;
import com.zhuanzhuan.hero.user.consts.NumConsts;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * @ClassName RetryUtil
 * @Description
 * @Author hanjunjun
 * @Date 2024/7/3 11:16
 */
@Slf4j
public class RetryUtil {

    /**
     * 重试，没有返回值
     *
     * @param runnable
     * @param retryThrowable
     * @param invokeTimes
     * @param retryInterval
     * @param timeUnit
     * @return void
     * @author  hanjunjun
     * @date 2024/7/6
     **/
    public static void retryRunable(Runnable runnable, Class<? extends Throwable> retryThrowable,
        int invokeTimes, int retryInterval, TimeUnit timeUnit) throws Throwable {
        //保留原始异常
        Throwable reserveEx = null;
        for (int haveRetryTimes = 0; haveRetryTimes < invokeTimes; haveRetryTimes++) {
            log.info("doCallable {} times", haveRetryTimes + 1);
            try {
                runnable.run();
                return;
            } catch (Throwable ex) {
                reserveEx = ex;
                //指定的异常，继续重试   不是，异常终止
                if (retryThrowable == null || retryThrowable.isAssignableFrom(ex.getClass())) {
                    if (timeUnit != null && retryInterval > 0) {
                        try {
                            timeUnit.sleep(retryInterval);
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                    }
                    log.info("invoke exception,will retry");
                } else {
                    log.info("invoke exception,will terminal");
                    throw ex;
                }
            }
        }

        if (reserveEx != null) {
            log.error("try best retry, but still failed. ex={}", Throwables.getStackTraceAsString(reserveEx));
            throw reserveEx;
        }

        throw new RuntimeException("try best retry, but still failed，no cause");
    }

    /**
     * 任何异常都重试，无间隔
     *  重试完还是失败，只打印error日志不会向上抛出异常
     * @param runnable
     * @return void
     * @author  hanjunjun
     * @date 2024/7/10
     **/
    public static void retryRunableNoInterval(Runnable runnable) {
        try {
            retryRunable(runnable, Exception.class, NumConsts.THREE, NumConsts.ZERO, null);
        } catch (Throwable e) {
            log.error("重试完成仍失败");
            throw new RuntimeException(e);
        }
    }

    /**
     * 有返回值的重试
     *
     * @param supplier 目标方法
     * @param retryThrowable 进行重试的异常，该异常及其子类都会重试，否则抛出原异常结束
     * @param invokeTimes  总共执行次数
     * @param retryInterval 重试间隔时间,<=0则没有间隔时间
     * @param timeUnit 间隔时间单位
     * @return V
     * @author  hanjunjun
     * @date 2024/1/3
     **/
    public static <V> V retryCallable(Supplier<V> supplier, Class<? extends Throwable> retryThrowable,
        int invokeTimes, int retryInterval, TimeUnit timeUnit) throws Throwable {
        //保留原始异常
        Throwable reserveEx = null;
        for (int haveRetryTimes = 0; haveRetryTimes < invokeTimes; haveRetryTimes++) {
            log.info("doSupplier {} times", haveRetryTimes + 1);
            try {
                return supplier.get();
            } catch (Throwable ex) {
                reserveEx = ex;
                //指定的异常，继续重试   不是，异常终止
                if (retryThrowable == null || retryThrowable.isAssignableFrom(ex.getClass())) {
                    if (timeUnit != null && retryInterval > 0) {
                        try {
                            timeUnit.sleep(retryInterval);
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                    }
                    log.info("invoke exception,will retry");
                } else {
                    log.info("invoke exception,will terminal");
                    throw ex;
                }
            }
        }

        if (reserveEx != null) {
            log.error("try best retry, but still failed. ex={}", Throwables.getStackTraceAsString(reserveEx));
            throw reserveEx;
        }

        throw new RuntimeException("try best retry, but still failed，no cause");
    }
}
